﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using HtmlAgilityPack;


namespace Nasa8x.Core
{
    public static class HtmlScrubber
    {

        #region SanitizeHtml

     



        public static string SanitizeHtml(this string html, string[] allowHtmlTags, string[] allowHtmlAttributes)
        {

            var _doc = new HtmlDocument();
            
            _doc.LoadHtml(html);

            string[] _elementWhitelist = allowHtmlTags;
            string[] _attributeWhiteList = allowHtmlAttributes;


            IList<HtmlNode> _hnc = _doc.DocumentNode.DescendantNodes().ToList();

            //remove non-white list nodes
            for (int i = _hnc.Count - 1; i >= 0; i--)
            {
                HtmlNode _htmlNode = _hnc[i];
                if (!_elementWhitelist.Contains(_htmlNode.Name.ToLower()))
                {
                    _htmlNode.Remove();
                    continue;
                }

                if (_htmlNode.Name.ToLower() == "pre")
                {
                    _htmlNode.InnerHtml = _htmlNode.OuterHtml.Replace("<br>", Environment.NewLine).Replace("<br />", Environment.NewLine);
                }

                for (int att = _htmlNode.Attributes.Count - 1; att >= 0; att--)
                {
                    HtmlAttribute _attribute = _htmlNode.Attributes[att];
                    //remove any attribute that is not in the white list (such as event handlers)
                    if (!_attributeWhiteList.Contains(_attribute.Name.ToLower()))
                    {
                        _attribute.Remove();
                    }

                    //strip any "style" attributes that contain the word "expression"
                    if (_attribute.Value.ToLower().Contains("expression") && _attribute.Name.ToLower() == "style")
                    {
                        _attribute.Value = string.Empty;
                    }


                    if (_attribute.Name.ToLower() == "src" || _attribute.Name.ToLower() == "href")
                    {
                        //strip if the link starts with anything other than http (such as jscript, javascript, vbscript, mailto, ftp, etc...)
                        if (!_attribute.Value.StartsWith("http") || !_attribute.Value.StartsWith("/")) _attribute.Value = "#";
                    }
                }
            }
            return _doc.DocumentNode.WriteTo();


        }


        public static string SanitizeHtml(this string html)
        {

            string[] _elementWhitelist = {
                                            "a", "u", "b", "i", "br", "br ", "br", "h1", "h2", "h3", "h4", "h5", "h6", "span","pre",
                                            "div", "blockquote", "em", "sub", "sup", "s", "font", "ul", "li", "ol", "p", "#text"
                                        };

            string[] _attributeWhiteList = { "class", "style", "src", "href", "color", "size", "width", "height", "align" };

            return html.SanitizeHtml(_elementWhitelist, _attributeWhiteList);


        }



        private static Regex _namedtags = new Regex
    (@"</?(?<tagname>\w+)[^>]*(\s|$|>)",
    RegexOptions.Singleline | RegexOptions.ExplicitCapture | RegexOptions.Compiled);

        /// <summary>
        /// attempt to balance HTML tags in the html string
        /// by removing any unmatched opening or closing tags
        /// IMPORTANT: we *assume* HTML has *already* been 
        /// sanitized and is safe/sane before balancing!
        /// 
        /// CODESNIPPET: A8591DBA-D1D3-11DE-947C-BA5556D89593
        /// </summary>
        public static string BalanceTags(string html)
        {
            if (String.IsNullOrEmpty(html)) return html;

            // convert everything to lower case; this makes
            // our case insensitive comparisons easier
            MatchCollection tags = _namedtags.Matches(html.ToLowerInvariant());

            // no HTML tags present? nothing to do; exit now
            int tagcount = tags.Count;
            if (tagcount == 0) return html;

            string tagname;
            string tag;
            const string ignoredtags = "<p><img><br><li><hr>";
            int match;
            var tagpaired = new bool[tagcount];
            var tagremove = new bool[tagcount];

            // loop through matched tags in forward order
            for (int ctag = 0; ctag < tagcount; ctag++)
            {
                tagname = tags[ctag].Groups["tagname"].Value;

                // skip any already paired tags
                // and skip tags in our ignore list; assume they're self-closed
                if (tagpaired[ctag] || ignoredtags.Contains("<" + tagname + ">"))
                    continue;

                tag = tags[ctag].Value;
                match = -1;

                if (tag.StartsWith("</"))
                {
                    // this is a closing tag
                    // search backwards (previous tags), look for opening tags
                    for (int ptag = ctag - 1; ptag >= 0; ptag--)
                    {
                        string prevtag = tags[ptag].Value;
                        if (!tagpaired[ptag] && prevtag.Equals("<" + tagname, StringComparison.InvariantCulture))
                        {
                            // minor optimization; we do a simple possibly incorrect match above
                            // the start tag must be <tag> or <tag{space} to match
                            if (prevtag.StartsWith("<" + tagname + ">") || prevtag.StartsWith("<" + tagname + " "))
                            {
                                match = ptag;
                                break;
                            }
                        }
                    }
                }
                else
                {
                    // this is an opening tag
                    // search forwards (next tags), look for closing tags
                    for (int ntag = ctag + 1; ntag < tagcount; ntag++)
                    {
                        if (!tagpaired[ntag] && tags[ntag].Value.Equals("</" + tagname + ">", StringComparison.InvariantCulture))
                        {
                            match = ntag;
                            break;
                        }
                    }
                }

                // we tried, regardless, if we got this far
                tagpaired[ctag] = true;
                if (match == -1)
                    tagremove[ctag] = true; // mark for removal
                else
                    tagpaired[match] = true; // mark paired
            }

            // loop through tags again, this time in reverse order
            // so we can safely delete all orphaned tags from the string
            for (int ctag = tagcount - 1; ctag >= 0; ctag--)
            {
                if (tagremove[ctag])
                {
                    html = html.Remove(tags[ctag].Index, tags[ctag].Length);
                    System.Diagnostics.Debug.WriteLine("unbalanced tag removed: " + tags[ctag]);
                }
            }

            return html;
        }


        #endregion
    }
}
